"""
Problem 1 solver.

- Builds the physical scenario from configuration.
- Uses a pluggable OcclusionStrategy to evaluate occlusion over time.
- Extracts cover intervals and total cover time.

This module exposes a `solve_q1()` function and a CLI entry for quick testing.
"""

from __future__ import annotations
from dataclasses import dataclass
from typing import Tuple, List, Any, Callable, Optional
import numpy as np
import math

from ..type.models import Missile, UAV, CloudModel, CylinderTarget, DropPlan
from .intervals import find_boolean_intervals, union_length, intervals_point_los_analytic
from .occlusion.base import PointLoSStrategy, CylinderSamplingStrategy, OcclusionStrategy


@dataclass
class Q1Config:
    # Target (cylinder)
    target_base_center: Tuple[float, float, float] = (0.0, 200.0, 0.0)
    target_radius: float = 7.0
    target_height: float = 10.0

    # Missile
    missile_M0: Tuple[float, float, float] = (20000.0, 0.0, 2000.0)
    missile_speed: float = 300.0
    missile_aim_to: Tuple[float, float, float] = (0.0, 0.0, 0.0)

    # UAV FY1 (heading toward origin at constant speed)
    uav_R0_xy: Tuple[float, float] = (17800.0, 0.0)
    uav_z: float = 1800.0
    uav_speed: float = 120.0

    # Drop plan timing
    t_rel: float = 1.5
    tau: float = 3.6

    # Cloud model
    cloud_radius: float = 10.0
    cloud_sink: float = 3.0
    cloud_life: float = 20.0
    g: float = 9.81

    # Time scan
    dt_scan: float = 0.005
    refine_iters: int = 60
    hit_margin: float = 0.5  # after missile hit time, we can stop


def solve_q1(cfg: Q1Config, strategy: OcclusionStrategy) -> dict[str, Any]:
    """Solve problem 1 with the given occlusion strategy. Returns a dict with results."""
    # Build scenario
    target = CylinderTarget(cfg.target_base_center, cfg.target_radius, cfg.target_height)
    missile = Missile(cfg.missile_M0, cfg.missile_speed, cfg.missile_aim_to)
    uav = UAV(cfg.uav_R0_xy, cfg.uav_z, cfg.uav_speed)
    cloud = CloudModel(cfg.cloud_radius, cfg.cloud_sink, cfg.cloud_life, cfg.g)
    plan = DropPlan(uav, cfg.t_rel, cfg.tau)
    plan.compute_detonation(cloud)
    # Time window: from detonation until the earlier of cloud expiry or missile "hit" time.
    dist_to_aim = float(np.linalg.norm(np.array(cfg.missile_aim_to, float) - np.array(cfg.missile_M0, float)))
    t_hit = dist_to_aim / cfg.missile_speed
    t0 = plan.t_det
    t1 = min(plan.t_det + cloud.life, t_hit + cfg.hit_margin)

    # Predicate for time scanning
    def is_occluded_at_time(t: float) -> bool:
        return bool(strategy.is_occluded(t, missile, target, plan, cloud))

    # Scan and refine edges
    
    intervals,total = intervals_point_los_analytic(
        missile.M0,missile.aim_to,missile.speed,target.center_point,plan.center_func(cloud)(plan.t_det),
        cloud.sink,plan.t_det,cloud.r,t0,t1
    )
    
    intervals = find_boolean_intervals(t0, t1, is_occluded_at_time, dt=cfg.dt_scan, refine_iters=cfg.refine_iters)
    total = union_length(intervals)


    result = {
        "t_det": plan.t_det,
        "xy_det": plan.xy_det,
        "z_det": plan.z_det,
        "intervals": intervals,
        "total_cover": total,
        "strategy": type(strategy).__name__,
    }
    return result

if __name__ == "__main__":
    # Default run: representative point LoS (center point)
    cfg = Q1Config()
    strategy = PointLoSStrategy(which_point="center")
    res = solve_q1(cfg, strategy)
    print(f"[{res['strategy']}] t_det={res['t_det']:.3f} s, xy_det={res['xy_det']}, z_det={res['z_det']:.3f} m")
    print("Intervals:", [(round(a, 3), round(b, 3)) for a, b in res["intervals"]])
    print("Total cover (s):", round(res["total_cover"], 3))

    # Example: cylinder sampling strategy (strict = threshold=1.0; majority = threshold=0.5)
    strategy2 = CylinderSamplingStrategy(n_circ=36, n_h=5, threshold=1.0)
    res2 = solve_q1(cfg, strategy2)
    print(f"\n[{res2['strategy']}] t_det={res2['t_det']:.3f} s, xy_det={res2['xy_det']}, z_det={res2['z_det']:.3f} m")
    print("Intervals:", [(round(a, 3), round(b, 3)) for a, b in res2['intervals']])
    print("Total cover (s):", round(res2["total_cover"], 3))